/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {ref, Ref} from 'vue'
import type {Graph} from '@antv/x6'
import {DagreLayout, GridLayout} from '@antv/layout'
import _ from 'lodash'
import {X6_NODE_NAME, X6_EDGE_NAME} from './dag-config'

interface Options {
    graph: Ref<Graph | undefined>
}

interface LayoutConfig {
    cols: number
    nodesep: number
    padding: number
    ranksep: number
    rows: number
    type: LAYOUT_TYPE
}

export enum LAYOUT_TYPE {
    GRID = 'grid',
    DAGRE = 'dagre'
}

/**
 * Auto layout graph
 * 1. Manage the state of auto layout popups
 * 2. Implement graph automatic layout function
 */
export function useGraphAutoLayout(options: Options) {
    const DEFAULT_LAYOUT_CONFIG: LayoutConfig = {
        cols: 0,
        nodesep: 50,
        padding: 50,
        ranksep: 50,
        rows: 0,
        type: LAYOUT_TYPE.DAGRE
    }

    const {graph: graphRef} = options

    // Auto layout config form ref
    const formRef = ref()

    // Auto layout config form value
    const formValue = ref({
        ...DEFAULT_LAYOUT_CONFIG
    })

    // Dag format modal visible
    const visible = ref<boolean>(false)
    const toggle = (bool?: boolean) => {
        if (typeof bool === 'boolean') {
            visible.value = bool
        } else {
            visible.value = !visible.value
        }
    }

    /**
     * Auto layout graph
     * @param layoutConfig
     * @returns
     */
    function format(layoutConfig: LayoutConfig) {
        if (Object.keys(layoutConfig).length === 0) {
            layoutConfig = DEFAULT_LAYOUT_CONFIG
        }
        const graph = graphRef?.value
        if (!graph) {
            return
        }

        graph.cleanSelection()

        let layoutFunc = null
        if (layoutConfig.type === LAYOUT_TYPE.DAGRE) {
            layoutFunc = new DagreLayout({
                type: LAYOUT_TYPE.DAGRE,
                rankdir: 'LR',
                align: 'UL',
                // Calculate the node spacing based on the edge label length
                ranksepFunc: (d) => {
                    const edges = graph.getOutgoingEdges(d.id)
                    let max = 0
                    if (edges && edges.length > 0) {
                        edges.forEach((edge) => {
                            const edgeView = graph.findViewByCell(edge)
                            const labelView = edgeView?.findAttr(
                                'width',
                                _.get(edgeView, ['labelSelectors', '0', 'body'], null)
                            )
                            const labelWidth = labelView ? +labelView : 0
                            max = Math.max(max, labelWidth)
                        })
                    }
                    return layoutConfig.ranksep + max
                },
                nodesep: layoutConfig.nodesep,
                controlPoints: true
            })
        } else if (layoutConfig.type === LAYOUT_TYPE.GRID) {
            layoutFunc = new GridLayout({
                type: LAYOUT_TYPE.GRID,
                preventOverlap: true,
                preventOverlapPadding: layoutConfig.padding,
                sortBy: '_index',
                rows: layoutConfig.rows || undefined,
                cols: layoutConfig.cols || undefined,
                nodeSize: 220
            })
        }
        const json = graph.toJSON()
        const nodes = json.cells
            .filter((cell) => cell.shape === X6_NODE_NAME)
            .map((item) => {
                return {
                    ...item,
                    // sort by code aesc
                    _index: -(item.id as string)
                }
            })
        const edges = json.cells.filter((cell) => cell.shape === X6_EDGE_NAME)
        const newModel: any = layoutFunc?.layout({
            nodes: nodes,
            edges: edges
        } as any)
        graph.fromJSON(newModel)
    }

    /**
     * Auto layout modal submit
     */
    function submit() {
        if (formRef.value) {
            formRef.value.validate((errors: unknown) => {
                if (errors) return
                format(formValue.value)
                toggle(false)
            })
        } else {
            format({} as LayoutConfig)
        }
    }

    /**
     * Auto layout modal cancel
     */
    function cancel() {
        toggle(false)
    }

    return {
        format,
        toggle,
        visible,
        formRef,
        formValue,
        cancel,
        submit
    }
}
